/**
 *  @file
 *  @copyright defined in eos/LICENSE.txt
 */
#pragma once
#include <eos/chain/types.hpp>
#include <eos/chain/message.hpp>

#include <numeric>

namespace eosio { namespace chain {

   /**
    * @defgroup transactions transactions
    *
    * All transactions are sets of messages that must be applied atomically (all succeed or all fail). Transactions
    * must refer to a recent block that defines the context of the operation so that they assert a known
    * state-precondition assumed by the transaction signers.
    *
    * Rather than specify a full block number, we only specify the lower 16 bits of the block number which means you
    * can reference any block within the last 65,536 blocks which is 2.2 days with a 3 second block interval.
    *
    * All transactions must expire so that the network does not have to maintain a permanent record of all transactions
    * ever published. A transaction may not have an expiration date too far in the future because this would require
    * keeping too much transaction history in memory.
    *
    * The block prefix is the first 4 bytes of the block hash of the reference block number, which is the second 4
    * bytes of the @ref block_id_type (the first 4 bytes of the block ID are the block number)
    *
    * @note It is not recommended to set the @ref ref_block_num, @ref ref_block_prefix, and @ref expiration
    * fields manually. Call @ref set_expiration instead.
    *
    * @{
    */

   struct processed_transaction;
   struct inline_transaction;
   struct processed_generated_transaction;


   /**
    * @brief methods that operate on @ref eosio::types::Transaction.
    *
    * These are utility methods for sharing common operations between inheriting types which define
    * additional features and requirements based off of @ref eosio::types::Transaction.
    */
   /// Calculate the digest for a transaction
   digest_type transaction_digest(const transaction& t);

   void transaction_set_reference_block(transaction& t, const block_id_type& reference_block);

   bool transaction_verify_reference_block(const transaction& t, const block_id_type& reference_block);

   template <typename T>
   void transaction_set_message(transaction& t, int index, const types::func_name& type, T&& value) {
      message m(t.messages[index]);
      m.set(type, std::forward<T>(value));
      t.messages[index] = m;
   }

   template <typename T>
   T transaction_message_as(transaction& t, int index) {
      message m(t.messages[index]);
      return m.as<T>();
   }

   template <typename... Args>
   void transaction_emplace_message(transaction& t, Args&&... a) {
      t.messages.emplace_back(message(std::forward<Args>(a)...));
   }

   template <typename... Args>
   void transaction_emplace_serialized_message(transaction& t, Args&&... a) {
      t.messages.emplace_back(types::message(std::forward<Args>(a)...));
   }

   /**
      * clear all common data
      */
   inline void transaction_clear(transaction& t) {
      t.messages.clear();
   }

   /**
    * @brief A generated_transaction is a transaction which was internally generated by the blockchain, typically as a
    * result of running a contract.
    *
    * When contracts run and seek to interact with other contracts, or mutate chain state, they generate transactions
    * containing messages which effect these interactions and mutations. These generated transactions are automatically
    * generated by contracts, and thus are authorized by the script that generated them rather than by signatures. The
    * generated_transaction struct records such a transaction.
    *
    * These transactions are generated while processing other transactions. The generated transactions are assigned a
    * sequential ID, then stored in the block that generated them. These generated transactions can then be included in
    * subsequent blocks by referencing this ID.
    */
   struct generated_transaction : public types::transaction {
      generated_transaction() = default;
      generated_transaction(generated_transaction_id_type _id, const transaction& trx)
         : types::transaction(trx)
         , id(_id) {}

      generated_transaction(generated_transaction_id_type _id, const transaction&& trx)
         : types::transaction(trx)
         , id(_id) {}
      
      generated_transaction_id_type id;

      digest_type merkle_digest() const;

      typedef processed_generated_transaction processed;
   };

   /**
    * @brief A transaction with signatures
    *
    * signed_transaction is a transaction with an additional manifest of authorizations included with the transaction,
    * and the signatures backing those authorizations.
    */
   struct signed_transaction : public types::signed_transaction {
      typedef types::signed_transaction super;
      using super::super;

      /// Calculate the id of the transaction
      transaction_id_type id()const;
      
      /// Calculate the digest used for signature validation
      digest_type         sig_digest(const chain_id_type& chain_id)const;

      /** signs and appends to signatures */
      const signature_type& sign(const private_key_type& key, const chain_id_type& chain_id);

      /** returns signature but does not append */
      signature_type sign(const private_key_type& key, const chain_id_type& chain_id)const;

      flat_set<public_key_type> get_signature_keys(const chain_id_type& chain_id)const;

      /**
       * Removes all messages, signatures, and authorizations
       */
      void clear() { transaction_clear(*this); signatures.clear(); }

      digest_type merkle_digest() const;

      typedef processed_transaction processed;
   };

   struct pending_inline_transaction : public types::transaction {
      typedef types::transaction super;
      using super::super;

      explicit pending_inline_transaction( const types::transaction& t ):types::transaction((const types::transaction& )t){}
      
      typedef inline_transaction processed;
   };

   struct message_output;
   struct inline_transaction : public types::transaction {
      explicit inline_transaction( const types::transaction& t ):types::transaction((const types::transaction& )t){}
      explicit inline_transaction( const pending_inline_transaction& t ):types::transaction((const types::transaction& )t){}
      inline_transaction(){}

      vector<message_output> output;
   };

   struct notify_output;
   
   /**
    *  Output generated by applying a particular message.
    */
   struct message_output {
      vector<notify_output>             notify; ///< accounts to notify, may only be notified once
      fc::optional<inline_transaction>  inline_trx; ///< transactions generated and applied after notify
      vector<generated_transaction>     deferred_trxs; ///< transactions generated but not applied
   };

   struct notify_output {
      account_name  name;
      message_output output;
   };

   struct processed_transaction : public signed_transaction {
      explicit processed_transaction( const signed_transaction& t ):signed_transaction(t){}
      processed_transaction(){}

      vector<message_output> output;
   };

   struct processed_generated_transaction {
      explicit processed_generated_transaction( const generated_transaction_id_type& _id ):id(_id){}
      explicit processed_generated_transaction( const generated_transaction& t ):id(t.id){}
      processed_generated_transaction(){}

      generated_transaction_id_type   id;
      vector<message_output> output;
   };
   /// @} transactions group

} } // eosio::chain

FC_REFLECT(eosio::chain::generated_transaction, (id))
FC_REFLECT_DERIVED(eosio::chain::signed_transaction, (eosio::types::signed_transaction), )
FC_REFLECT(eosio::chain::message_output, (notify)(inline_trx)(deferred_trxs) )
FC_REFLECT_DERIVED(eosio::chain::processed_transaction, (eosio::types::signed_transaction), (output) )
FC_REFLECT_DERIVED(eosio::chain::pending_inline_transaction, (eosio::types::transaction), )
FC_REFLECT_DERIVED(eosio::chain::inline_transaction, (eosio::types::transaction), (output) )
FC_REFLECT(eosio::chain::processed_generated_transaction, (id)(output) )
FC_REFLECT(eosio::chain::notify_output, (name)(output) )
